home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / ruby / 1.8 / observer.rb < prev    next >
Text File  |  2007-02-12  |  5KB  |  193 lines

  1. #
  2. # observer.rb implements the _Observer_ object-oriented design pattern.  The
  3. # following documentation is copied, with modifications, from "Programming
  4. # Ruby", by Hunt and Thomas; http://www.rubycentral.com/book/lib_patterns.html.
  5. #
  6. # == About
  7. #
  8. # The Observer pattern, also known as Publish/Subscribe, provides a simple
  9. # mechanism for one object to inform a set of interested third-party objects
  10. # when its state changes. 
  11. #
  12. # == Mechanism
  13. #
  14. # In the Ruby implementation, the notifying class mixes in the +Observable+
  15. # module, which provides the methods for managing the associated observer
  16. # objects.
  17. #
  18. # The observers must implement the +update+ method to receive notifications.
  19. #
  20. # The observable object must:
  21. # * assert that it has +changed+
  22. # * call +notify_observers+
  23. #
  24. # == Example
  25. #
  26. # The following example demonstrates this nicely.  A +Ticker+, when run,
  27. # continually receives the stock +Price+ for its +@symbol+.  A +Warner+ is a
  28. # general observer of the price, and two warners are demonstrated, a +WarnLow+
  29. # and a +WarnHigh+, which print a warning if the price is below or above their
  30. # set limits, respectively.
  31. #
  32. # The +update+ callback allows the warners to run without being explicitly
  33. # called.  The system is set up with the +Ticker+ and several observers, and the
  34. # observers do their duty without the top-level code having to interfere.
  35. #
  36. # Note that the contract between publisher and subscriber (observable and
  37. # observer) is not declared or enforced.  The +Ticker+ publishes a time and a
  38. # price, and the warners receive that.  But if you don't ensure that your
  39. # contracts are correct, nothing else can warn you.
  40. #
  41. #   require "observer"
  42. #   
  43. #   class Ticker          ### Periodically fetch a stock price.
  44. #     include Observable
  45. #   
  46. #     def initialize(symbol)
  47. #       @symbol = symbol
  48. #     end
  49. #   
  50. #     def run
  51. #       lastPrice = nil
  52. #       loop do
  53. #         price = Price.fetch(@symbol)
  54. #         print "Current price: #{price}\n"
  55. #         if price != lastPrice
  56. #           changed                 # notify observers
  57. #           lastPrice = price
  58. #           notify_observers(Time.now, price)
  59. #         end
  60. #         sleep 1
  61. #       end
  62. #     end
  63. #   end
  64. #
  65. #   class Price           ### A mock class to fetch a stock price (60 - 140).
  66. #     def Price.fetch(symbol)
  67. #       60 + rand(80)
  68. #     end
  69. #   end
  70. #   
  71. #   class Warner          ### An abstract observer of Ticker objects.
  72. #     def initialize(ticker, limit)
  73. #       @limit = limit
  74. #       ticker.add_observer(self)
  75. #     end
  76. #   end
  77. #   
  78. #   class WarnLow < Warner
  79. #     def update(time, price)       # callback for observer
  80. #       if price < @limit
  81. #         print "--- #{time.to_s}: Price below #@limit: #{price}\n"
  82. #       end
  83. #     end
  84. #   end
  85. #   
  86. #   class WarnHigh < Warner
  87. #     def update(time, price)       # callback for observer
  88. #       if price > @limit
  89. #         print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
  90. #       end
  91. #     end
  92. #   end
  93. #
  94. #   ticker = Ticker.new("MSFT")
  95. #   WarnLow.new(ticker, 80)
  96. #   WarnHigh.new(ticker, 120)
  97. #   ticker.run
  98. #
  99. # Produces:
  100. #
  101. #   Current price: 83
  102. #   Current price: 75
  103. #   --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
  104. #   Current price: 90
  105. #   Current price: 134
  106. #   +++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
  107. #   Current price: 134
  108. #   Current price: 112
  109. #   Current price: 79
  110. #   --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79
  111.  
  112.  
  113. #
  114. # Implements the Observable design pattern as a mixin so that other objects can
  115. # be notified of changes in state.  See observer.rb for details and an example.
  116. #
  117. module Observable
  118.  
  119.   #
  120.   # Add +observer+ as an observer on this object. +observer+ will now receive
  121.   # notifications.
  122.   #
  123.   def add_observer(observer)
  124.     @observer_peers = [] unless defined? @observer_peers
  125.     unless observer.respond_to? :update
  126.       raise NoMethodError, "observer needs to respond to `update'" 
  127.     end
  128.     @observer_peers.push observer
  129.   end
  130.  
  131.   #
  132.   # Delete +observer+ as an observer on this object. It will no longer receive
  133.   # notifications.
  134.   #
  135.   def delete_observer(observer)
  136.     @observer_peers.delete observer if defined? @observer_peers
  137.   end
  138.  
  139.   #
  140.   # Delete all observers associated with this object.
  141.   #
  142.   def delete_observers
  143.     @observer_peers.clear if defined? @observer_peers
  144.   end
  145.  
  146.   #
  147.   # Return the number of observers associated with this object.
  148.   #
  149.   def count_observers
  150.     if defined? @observer_peers
  151.       @observer_peers.size
  152.     else
  153.       0
  154.     end
  155.   end
  156.  
  157.   #
  158.   # Set the changed state of this object.  Notifications will be sent only if
  159.   # the changed +state+ is +true+.
  160.   #
  161.   def changed(state=true)
  162.     @observer_state = state
  163.   end
  164.  
  165.   #
  166.   # Query the changed state of this object.
  167.   #
  168.   def changed?
  169.     if defined? @observer_state and @observer_state
  170.       true
  171.     else
  172.       false
  173.     end
  174.   end
  175.  
  176.   #
  177.   # If this object's changed state is +true+, invoke the update method in each
  178.   # currently associated observer in turn, passing it the given arguments. The
  179.   # changed state is then set to +false+.
  180.   #
  181.   def notify_observers(*arg)
  182.     if defined? @observer_state and @observer_state
  183.       if defined? @observer_peers
  184.     for i in @observer_peers.dup
  185.       i.update(*arg)
  186.     end
  187.       end
  188.       @observer_state = false
  189.     end
  190.   end
  191.  
  192. end
  193.